package cn.ibent.http;

import java.util.Arrays;
import java.util.List;

import javax.net.ssl.SSLException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.ibent.common.ExceptionUtil;
import cn.ibent.common.SocketChannelUtils;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.ssl.ClientAuth;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslProvider;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;

import static cn.ibent.BootStrap.clazzSocketChannel;

public class RelayHandler extends SimpleChannelInboundHandler<HttpObject> {
    private static Logger logger = LoggerFactory.getLogger(RelayHandler.class.getSimpleName());


    private int remotePort = 443;
    private String remoteHost = "goproxy.cn";
    private final Bootstrap b = new Bootstrap();
    private static SslContext sslContext;

    static {
        // 解决algid parse error, not a sequence
        // https://blog.csdn.net/ls0111/article/details/77533768
        java.security.Security.addProvider(
                new org.bouncycastle.jce.provider.BouncyCastleProvider()
        );
        List<String> ciphers = Arrays.asList("ECDHE-RSA-AES128-SHA", "ECDHE-RSA-AES256-SHA", "AES128-SHA", "AES256-SHA", "DES-CBC3-SHA");
        try {
            sslContext = SslContextBuilder.forClient()
                    .sslProvider(SslProvider.OPENSSL)
                    .clientAuth(ClientAuth.NONE)
                    .trustManager(InsecureTrustManagerFactory.INSTANCE)
//                    .ciphers(ciphers)
                    .build();
        } catch (SSLException e) {
            e.printStackTrace();
        }
    }
    public RelayHandler(boolean autoRelease) {
        super(autoRelease);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
        b.group(ctx.channel().eventLoop())
                .channel(clazzSocketChannel)
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
                .option(ChannelOption.SO_KEEPALIVE, true)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        
                    }
                });
        b.connect(remoteHost, remotePort).addListener(new ChannelFutureListener() {

            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                if (future.isSuccess()) {
                    Channel outboud = future.channel();
                    outboud.pipeline().addLast(sslContext.newHandler(ctx.alloc()));
                    outboud.pipeline().addLast(new SslEventHandler(ctx.channel()));
                    outboud.writeAndFlush(Unpooled.wrappedBuffer("".getBytes()));
                } else {
                    logger.error("connect to: {}:{} failed! == {}", remoteHost, remotePort, ExceptionUtil.getMessage(future.cause()));
                    SocketChannelUtils.closeOnFlush(ctx.channel());
                }
            }
            
        });
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        if (msg instanceof HttpRequest httpMsg) {
            HttpHeaders headers = httpMsg.headers();
            headers.remove("Host");
            headers.add("Host", remoteHost + ":" + remotePort);
        }
        ctx.fireChannelRead(msg);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        logger.warn(ctx.channel().remoteAddress() + " " + ExceptionUtil.getMessage(cause));
        ctx.close();
    }

}
